/*=========================================================================
| KCardDAV
|--------------------------------------------------------------------------
| (c) 2009  Kumaran Santhanam
|
| This project is released under the GNU General Public License.
| Please see the file COPYING for more details.
|--------------------------------------------------------------------------
| Main interface to the KResource system.
 ========================================================================*/

/*=========================================================================
| INCLUDES
 ========================================================================*/

#include "resource.h"
#include "download.h"
#include "upload.h"

#include <QtCore/QString>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>

#ifdef KCARDDAV_DEBUG
    #include <QtCore/QFile>
    #include <QtCore/QTextStream>
#endif

#include <neon/ne_socket.h>

#include <queue>

/*=========================================================================
| NAMESPACE
 ========================================================================*/

using namespace KABC;

/*=========================================================================
| UTILITY
 ========================================================================*/

#define log(s)  kDebug() << identifier() << s;

/*=========================================================================
| IMPL
 ========================================================================*/

struct ResourceCardDav::Impl {
    ResourceCardDav* owner;
    CardDavPrefs* prefs;
    QMutex mutex;
    std::queue< carddav::Job* > jobs;

    Impl(ResourceCardDav* Owner);
    ~Impl();

    QString identifier() const;

    /**
     * Creates prefs and configures them.
     * @return a newly created preferences object. It should be removed by the caller.
     */
    CardDavPrefs* createPrefs() const;

    void pushJob(carddav::Job* job);
    carddav::Job* frontJob() const;
    void popJob();
    void ensureFrontJobRunning() const;
    void clearJobs();
};

ResourceCardDav::Impl::Impl(ResourceCardDav* Owner)
    : owner(Owner)
{
    log("");
    // init neon
    if (ne_sock_init()) {
        log("neon initialization failed");
    }
    // init resource
    prefs = createPrefs();
    owner->setType("ResourceCardDav");
}

ResourceCardDav::Impl::~Impl() {
    clearJobs();
    ne_sock_exit();
}

QString ResourceCardDav::Impl::identifier() const {
    return owner->identifier();
}

CardDavPrefs* ResourceCardDav::Impl::createPrefs() const {
    log("");
    CardDavPrefs* p = new CardDavPrefs(identifier());
    return p;
}

void ResourceCardDav::Impl::pushJob(carddav::Job* job) {
    jobs.push(job);
}

carddav::Job* ResourceCardDav::Impl::frontJob() const {
    return jobs.empty() ? 0 : jobs.front();
}

void ResourceCardDav::Impl::popJob() {
    if (!jobs.empty()) {
        carddav::Job* j = jobs.front();
        if (j->isRunning()) {
            j->wait();
        }
        delete j;
        jobs.pop();
    }
}

void ResourceCardDav::Impl::ensureFrontJobRunning() const {
    if (!jobs.empty()) {
        carddav::Job* j = jobs.front();
        if (!j->isRunning() && !j->isFinished()) {
            log("front job started");
            j->start();
        }
    }
}

void ResourceCardDav::Impl::clearJobs() {
    while(!jobs.empty()) {
        popJob();
    }
}

/*=========================================================================
| CONSTRUCTOR / DESTRUCTOR
 ========================================================================*/

ResourceCardDav::ResourceCardDav()
    : ResourceCached()
    , m_i(new Impl(this))
{
    log("");
}

ResourceCardDav::ResourceCardDav( const KConfigGroup &group )
    : ResourceCached(group)
    , m_i(new Impl(this))
{
    log("");
    readConfig(group);
}

ResourceCardDav::~ResourceCardDav() {
    log("");
    delete m_i;
}

/*=========================================================================
| GENERAL METHODS
 ========================================================================*/

CardDavPrefs* ResourceCardDav::prefs() {
    return m_i->prefs;
}

const CardDavPrefs* ResourceCardDav::prefs() const {
    return m_i->prefs;
}

void ResourceCardDav::readConfig( const KConfigGroup &/*group*/ ) {
    log("");
    m_i->prefs->readConfig();
}

void ResourceCardDav::writeConfig( KConfigGroup &group ) {
    log("");
    m_i->prefs->writeConfig();
    ResourceCached::writeConfig(group);
}

Ticket* ResourceCardDav::requestSaveTicket() {
    log("");
    return createTicket(this);
}

void ResourceCardDav::releaseSaveTicket( Ticket* ticket ) {
    log("");
    delete ticket;
}

QMutex* ResourceCardDav::mutex() const {
    return &m_i->mutex;
}

/*=========================================================================
| READING METHODS
 ========================================================================*/

bool ResourceCardDav::load() {
    log("");
    carddav::DownloadJob* job = new carddav::DownloadJob(m_i->prefs->getFullUrl());
    job->syncStart();
    QString error_info = job->errorInfo();
    int error = job->error();
    job->finish(this);
    delete job;
    return !error;
}

bool ResourceCardDav::asyncLoad() {
    log("");
    loadFromCache();
    carddav::DownloadJob* job = new carddav::DownloadJob(m_i->prefs->getFullUrl());
    QObject::connect(job, SIGNAL(finished()), this, SLOT(onLoadingFinished()));
    m_i->pushJob(job);
    m_i->ensureFrontJobRunning();
    return true;
}

/*=========================================================================
| WRITING METHODS
 ========================================================================*/

bool ResourceCardDav::save( Ticket* /*ticket*/ ) {
    log("");
    bool ret = true;
    if (hasChanges()) {
        saveToCache();
        carddav::UploadJob* job = new carddav::UploadJob(m_i->prefs->getFullUrl(),
                                                         addedAddressees(),
                                                         changedAddressees(),
                                                         deletedAddressees()
                                                         );
        job->syncStart();
        QString error_info = job->errorInfo();
        int error = job->error();
        job->finish(this);
        delete job;
        ret = !error;
    } else {
        log("no changes, saving is not needed");
    }
    return ret;
}

bool ResourceCardDav::asyncSave( Ticket* /*ticket*/ ) {
    log("");
    if (hasChanges()) {
        saveToCache();
        carddav::UploadJob* job = new carddav::UploadJob(m_i->prefs->getFullUrl(),
                                                         addedAddressees(),
                                                         changedAddressees(),
                                                         deletedAddressees()
                                                         );
        QObject::connect(job, SIGNAL(finished()), this, SLOT(onSavingFinished()));
        m_i->pushJob(job);
        m_i->ensureFrontJobRunning();
    } else {
        log("no changes, saving is not needed");
        emit savingFinished(this);
    }
    return true;
}

/*=========================================================================
| PRIVATE METHODS
 ========================================================================*/

void ResourceCardDav::onLoadingFinished() {
    carddav::Job* j = m_i->frontJob();
    if (!j) {
        log("jobs queue is empty or a job is null. Strange...");
        return;
    }
    QString error_info = j->errorInfo();
    int error = j->error();
    j->finish(this);
    m_i->popJob();
    m_i->ensureFrontJobRunning();

    if (error) {
        log("loading failed");
        emit loadingError(this, error_info);
    } else {
        log("loading finished successfully");
        emit loadingFinished(this);
    }
}

void ResourceCardDav::onSavingFinished() {
    carddav::Job* j = m_i->frontJob();
    if (!j) {
        log("jobs queue is empty or a job is null. Strange...");
        return;
    }
    QString error_info = j->errorInfo();
    int error = j->error();
    j->finish(this);
    m_i->popJob();
    m_i->ensureFrontJobRunning();

    if (error) {
        log("saving failed");
        emit savingError(this, error_info);
    } else {
        log("saving finished successfully");
        emit savingFinished(this);
    }
}

// EOF ========================================================================
